import {
  array,
  DecoderFunction,
  literal,
  optional,
  record,
  string,
  union,
} from 'typescript-json-decoder';
import { GeneralModel } from '../runtime';

export type ViewModelSchema = {
  properties: Array<
    | {
        type: 'string' | 'number' | 'boolean';
        name: string;
        default?: string;
      }
    | {
        type: 'object';
        name: string;
        default?: ViewModelSchema;
      }
    | {
        type: 'array';
        name: string;
      }
  >;
};

/**
 * Validate the given plain JSON object and transform into the typed schema.
 */
const validateViewModelData: DecoderFunction<ViewModelSchema> = record({
  properties: array(
    union(
      record({
        type: union(literal('string'), literal('number'), literal('boolean')),
        name: string,
        default: optional(string),
      }),
      record({
        type: literal('object'),
        name: string,
        default: optional((pojo) => validateViewModelData(pojo)),
      }),
      record({
        type: literal('array'),
        name: string,
      }),
    ),
  ),
});

/**
 * Create View Model from schema.
 *
 * @param schema the schema of View Model.
 * @returns the view model.
 */
function createViewModelFromSchema(
  schema: ViewModelSchema,
  path: string[] = [],
): GeneralModel {
  return schema.properties.reduce((vm, prop) => {
    switch (prop.type) {
      case 'array':
        vm[prop.name] = [];
        break;
      case 'boolean':
        vm[prop.name] = prop.default === 'true';
        break;
      case 'number':
        vm[prop.name] = prop.default ? parseFloat(prop.default) : 0;
        break;
      case 'string':
        vm[prop.name] = prop.default ?? '';
        break;
      case 'object':
        vm[prop.name] = prop.default
          ? createViewModelFromSchema(prop.default, [...path, prop.name])
          : null;
        break;
      default:
        throw new Error(`unexpected type.`);
    }
    return vm;
  }, {} as GeneralModel);
}

export function compileModelData(json: unknown): GeneralModel {
  return createViewModelFromSchema(validateViewModelData(json as any));
}
